package nebula

import (
	"errors"
	"fmt"
	"io"
	"net"
	"net/url"
	"runtime/debug"
	"time"

	"gddgame.cc/galaxy/satellite"
	"gddgame.cc/galaxy/satellite/compress/snappy"

	"gddgame.cc/galaxy/satellite/transport/kcp"
	"gddgame.cc/galaxy/satellite/transport/tcp"
	"gddgame.cc/galaxy/satellite/transport/ws"

	"gddgame.cc/galaxy/utils/def"
)

type Listener interface {
	OnConnect(success bool)
	OnReconnect(success bool)
	OnDisconnect()
}
type Client struct {
	def.Logger
	*satellite.GatewayClient
	pp *satellite.PingPongClient

	listener Listener

	sid  string
	root *clientNamespace
	app  *AppPacket

	exitStatus ExitStatus

	lastTimestamp int64
}

func NewClient() *Client {
	s := &Client{}

	s.GatewayClient = satellite.NewGatewayClient(s)
	s.pp = satellite.NewPPClient(s)
	s.root = newClientNamespace(s, NSP{})
	s.SetCompress(snappy.NewSnappy())

	s.lastTimestamp = 0
	return s
}

func (s *Client) SetListener(listener Listener) {
	s.listener = listener
}
func (s *Client) Tcp(addr string) error {
	tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
	if err != nil {
		fmt.Println(err)
		return err
	}
	if err := s.AddTransport(tcp.NewTransport(tcpAddr)); err != nil {
		return err
	}
	return nil
}

func (s *Client) Udp(addr string) error {
	udpAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		return err
	}
	if err := s.AddTransport(kcp.NewTransport(udpAddr)); err != nil {
		return err
	}
	return nil
}

func (s *Client) WebSocket(webSocket string) error {
	u, err := url.Parse(webSocket + "/galaxy.socket")
	if err != nil {
		return err
	}
	if err := s.AddTransport(ws.NewTransportClient(u)); err != nil {
		return err
	}
	return nil
}

func (s *Client) Of(ns NS) (*clientNamespace, error) {
	return s.root.Of(ns)
}

func (s *Client) With(nsp NSP) (*clientNamespace, error) {
	return s.root.With(nsp)
}

func (s *Client) Root() (*clientNamespace, error) {
	return s.root, nil
}

func (s *Client) ID() string {
	return s.sid
}

func (s *Client) SetApp(appId string, version string) {
	s.app = NewAppData()
	s.app.Id = appId
	s.app.Version = version
}

func (s *Client) Dial() error {
	if s.app == nil {
		return fmt.Errorf("Please set app info")
	}
	return s.GatewayClient.Dial()
}

func (s *Client) Close() error {
	if s.IsRunning() {
		s.Debugf("[ SOCKET CLIENT ] send close")
		s.SendByte(P_CLOSE, false, nil)
	}
	s.exitStatus = ClientExit
	_ = s.root.Disconnect()
	return s.GatewayClient.OnClose("forced client close")
}

func (s *Client) Ping() {
	s.Debugf("[ SOCKET CLIENT ] send ping")
	s.pp.OnPing()
	s.SendByte(P_PING, false, nil)
}

func (s *Client) SendPong(bytes []byte) {
	s.SendByte(P_PONG, false, bytes)
}

func (s *Client) Send(mt MT, compress bool, data satellite.DataPacket) {
	err := s.WriteSelect(byte(mt), compress, data.Encode)
	if err != nil {
		s.Error(err)
	} else {
		s.lastTimestamp = time.Now().Unix()
	}
}

func (s *Client) SendByte(mt MT, compress bool, data []byte) {
	err := s.WriteSelect(byte(mt), compress, func(writer io.Writer) error {
		if data != nil {
			_, err := writer.Write(data)
			return err
		} else {
			return nil
		}
	})
	if err != nil {
		s.Error(err)
	} else {
		s.lastTimestamp = time.Now().Unix()
	}
}

func (s *Client) sendByte(mt MT, conn satellite.Conn, compress bool, data []byte) {
	_ = s.Write(byte(mt), conn, compress, func(writer io.Writer) error {
		if data != nil {
			_, err := writer.Write(data)
			return err
		} else {
			return nil
		}
	})
}
func (s *Client) Packet(packet *NsData, ns *clientNamespace) {
	compress := false
	if ns != nil {
		compress = ns.compress
	}
	s.Debugf("[ SOCKET CLIENT ] send namespace:%#v %#v", ns.nsp, packet.Type)
	go s.Send(P_NAMESPACE, compress, &NsDataArray{packet})
}

func (s *Client) onClose() {
	s.exitStatus = ServerExit
	if s.listener != nil {
		s.listener.OnDisconnect()
	}
	_ = s.root.Disconnect()
	debug.PrintStack()
	_ = s.GatewayClient.OnClose("forced server close")
}

func (s *Client) onOpen(sid string) {
	s.sid = sid

	b, _ := s.app.MarshalMsg(nil)
	if s.State() == satellite.RECONNECTING {
		if s.listener != nil {
			s.listener.OnReconnect(true)
		}
		s.GatewayClient.UpdateState()
		s.SendByte(P_APPLICATION, false, b)
		s.root.reconnect()
	} else if s.State() == satellite.CONNECTING {
		if s.listener != nil {
			s.listener.OnConnect(true)
		}
		s.GatewayClient.UpdateState()
		s.SendByte(P_APPLICATION, false, b)
	}
}

func (s *Client) onReopen() {
	b, _ := s.app.MarshalMsg(nil)
	if s.State() == satellite.RECONNECTING {
		if s.listener != nil {
			s.listener.OnReconnect(true)
		}
		s.GatewayClient.UpdateState()
		s.SendByte(P_APPLICATION, false, b)
	} else if s.State() == satellite.CONNECTING {
		if s.listener != nil {
			s.listener.OnConnect(true)
		}
		s.GatewayClient.UpdateState()
		s.SendByte(P_APPLICATION, false, b)
	}
}

func (s *Client) onProtocol(p string) {
	u, err := url.Parse(p)
	if err != nil {
		s.Error(err)
		return
	} else {
		//s.Debugf("[ SOCKET CLIENT ] accept protocol:%#v, %#v", u.Scheme, u.Host)
		switch satellite.TransportType(u.Scheme) {
		case satellite.SpeedTransport:
			err := s.Udp(u.Host)
			if err != nil {
				//fmt.Println("udp")
				s.Error(err)
			}
		case satellite.DefaultTransport:
			err := s.Tcp(u.Host)
			if err != nil {
				//fmt.Println("tcp")
				s.Error(err)
			}
		default:
			s.Error(errors.New("protocol is error:" + p))
		}
	}
}
func (s *Client) onNamespace(message satellite.Message) {
	//defer func() {
	//	if err := recover(); err != nil {
	//		debug.PrintStack()
	//		s.Error(err)
	//	}
	//}()
	p := NewNsDataArray()
	message.ReadData(p)
	for _, d := range *p {
		go s.onDecode(d)
	}
}

func (s *Client) onDecode(packet *NsData) {
	ns := s.root
	if packet.NSP != nil {
		for _, n := range packet.NSP {
			ns, _ := ns.Of(NS(n))
			if ns == nil {
				_ = s.OnClose("namespace format error")
				return
			}
		}
	}
	ns.dispatch(packet)
}

func (s *Client) Dispatch(conn satellite.Conn, code byte, message satellite.Message) {
	t := MT(code)
	switch t {
	case P_OPEN:
		b := message.ReadString()
		go s.onOpen(b)
	case P_REOPEN:
		go s.onReopen()
	case P_CLOSE:
		s.Debugf("[ SOCKET CLIENT ] accept close")
		go s.onClose()
	case P_PONG:
		s.Debugf("[ SOCKET CLIENT ] accept pong")
		s.pp.OnPong(message)
	case P_NAMESPACE:
		s.onNamespace(message)
	default:

	}
}

func (s *Client) OnConnect(conn satellite.Conn, success bool) {
	if !success {
		if s.listener != nil {
			s.listener.OnConnect(false)
		}
		return
	}
	// 执行协议链接
	if s.sid != "" {
		s.Debugf("[ SOCKET CLIENT ] send reopen:%#v", s.sid)
		s.sendByte(P_REOPEN, conn, false, []byte(s.sid))
	} else {
		s.Debugf("[ SOCKET CLIENT ] send open")
		s.sendByte(P_OPEN, conn, false, nil)
	}
}
